﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Diagnostics;
using RAD.LightOne.Cache;
using System.Data;
using System.Reflection;
using RAD.LightOne.Data;
using System.Data.Common;


namespace RAD.LightOne
{
    #region Documentation
    ///<summary>
    /// Classe interna especializada em busca a MyObject.
    /// Tornada em separado para permitir eficiência com várias threads.
    ///</summary>
    #endregion
    internal sealed class Finder
    {
        private static object locker;

        static Finder()
        {
            locker = new object();
        }

        internal Finder() { }

        #region Documentation
        /// <summary>
        /// Metodo que realiza efetivamente o processo de extração dos dados do banco para a classe.
        /// </summary>
        /// <param name="type">Tipo alvo da consulta.</param>
        /// <param name="id">identificador único da instância.</param>
        /// <param name="trx">transação utilizada para efetuar o(s) comandos no banco.</param>
        /// <returns>MyObject  fortemente tipado.</returns>
        #endregion
        internal MyObject FindById(Type type, Guid id, IDbTransaction trx)
        {
#if TRACE
            Trace.WriteLine(string.Format("Entrou -> {0}", type));
#endif

            MyObjectsCache cache = new MyObjectsCache(trx);
            MyObject found = cache[id];
            if (found == null)
            {

                MyObject.VerifyIsIMyObject(type);
                InternalObj structure = null;
                if (type == typeof(MyObject))
                //if (structure[0].Name == type.Name)
                {
                    #region Comments
                    /*
                 * secundário, neste caso, nenhum atributo DBTable foi encontrado, uma execução secundária será utilizada para
                 * se definir o tipo correto e retornar a instância do objeto, uma busca baseada nas classes dentro do pool de tipos
                 * será realizada.
                 */
                    #endregion Comments
                    Finder finder = new Finder();
                    return finder.FindById(MetadataReader.GetRealTypeOf(type, id, trx), id, trx);
                }
                else
                {
                    structure = InternalObjBuilder.GetReadStructure(type, DataOperations.Select);
                }
                

                StringBuilder sql = InternalObjProcessor.BuildSelectQuery(structure);

                DBEngine dbe = DBEngine.GetInstance(trx);
                dbe.Parameters.Clear();
                IDataParameter param = dbe.CreateParameter();
                //param.ParameterName = structure[0][Global.ID].GetParameterName();
                param.ParameterName = structure[0][0].GetParameterName();
                param.Value = id.ToString();
                dbe.Parameters.Add(param);
                dbe.CommandText = sql.ToString();

                try
                {
                    //lock (locker)
                    {
                        using (IDataReader reader = dbe.ExecuteReader())
                        {
                            if (reader.Read())
                            {
                                for (int i = 0; i < structure.Count; i++)
                                {
                                    foreach (Field fItem in structure[i])
                                    {
                                        structure[i][fItem.Name].Value = reader[fItem.Name];
                                    }
                                }
                            }
                            else
                            {
                                return null;
                            }
                        }
                    }

                    TypesPool appTypes = new TypesPool();
                    LightTypesComparer comparer = new LightTypesComparer();

                    if (structure[0][Global.CLASS_NAME].Value.ToString() == type.FullName ||
                        comparer.Compare(type, appTypes[structure[0][Global.CLASS_NAME].Value.ToString()]) == 0)
                    {
                        type = appTypes[structure[0][Global.CLASS_NAME].Value.ToString()];

                        #region Comments
                        /*
                        * Melhor caso: todos os dados já foram recuperados,o tipo passado é igual ao tipo recuparado ou equivalente a este.
                        */
                        #endregion
                        //Type[] constructorTypes = new Type[1] { typeof(Guid) };
                        //ConstructorInfo[] constructors = type.GetConstructors(BindingFlags.NonPublic | BindingFlags.Instance);
                        ConstructorInfo[] constructors = type.GetConstructors(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance);
                        //int i = 0;
                        /*foreach (ConstructorInfo construc in constructors.Where(c=>c.GetParameters().Count() == 0))
                        {
                            ParameterInfo[] parms = construc.GetParameters();
                            if (parms.Length == 1 && parms[0].ParameterType == typeof(Guid))
                            {
                                break;
                            }
                            i++;
                        }

                        object[] parameters = new object[1] { id };
                        found = constructors[i].Invoke(parameters) as MyObject;
                        */

                        ConstructorInfo stdCtor = constructors.Where(c => c.GetParameters().Count() == 0).FirstOrDefault();
                        if (stdCtor == null)
                        {
                            throw new InvalidProgramException(string.Format("The type {0} has no default void constructor public, internal or private.", type.Name));
                        }
                        found = stdCtor.Invoke(null) as MyObject;
                        found.Id = id;
                        #region Comments
                        /*
                         * Adicionando a instância aqui, previno contra redundância cíclica.
                         */
                        #endregion
                        cache.Add(found);

                        structure.SuppressKeyOnDependantTables();

                        found.CallOnLoading(trx);
                        structure.SetValuesToInstance(found, trx);
                        found.CallOnLoaded(trx);
                    }
                    else
                    {
                        #region Comments
                        /*
                     * Pior caso: o tipo passado era abstrato ou não era equivalente ao tipo real, que é um tipo herdado do tipo passado,
                     * uma busca pelo tipo correto deve acontecer e as tentativas se darão do menor para o maior custo de processamento.
                     * Neste caso uma nova ida ao banco de dados acontecerá fatalmente, não achei que o custo dessa nova operação
                     * fosse alto o suficiente para o aproveitamento dos dados previamente recuperados, analizarei no futuro, e em sendo
                     * assim, é retornado um novo find, desta vez, do tipo correto.
                     */
                        #endregion
                        Finder finder = new Finder();
                        return finder.FindById(appTypes[structure[0][Global.CLASS_NAME].Value.ToString()], id, trx);

                    }
                    return found as MyObject;

                }
                catch (ArgumentException except)
                {
                    throw except;
                }
                catch (DbException except)
                {
                    throw new System.Data.InvalidExpressionException("Revise your sql expression. " + except.Message, except);
                }
                catch (InvalidProgramException except)
                {
                    throw except;
                }
                catch (Exception except)
                {
                    throw new InvalidProgramException("LightOne is unable to load the object typed as: " + structure.Type.FullName + " .", except);
                }
            }
            else
            {
#if TRACE
                Trace.WriteLine(string.Format("Saiu -> {0}", type));
#endif
                return found;
            }
        }
    }
}
